/*
* curl is a C++ wrapper for curl (https://curl.haxx.se/).
*
* @author: dzlua
* @contact: 505544956@qq.com
* @date: September 28, 2020
* @example: https://gitee.com/dzlua/curl.git
*/

#ifndef __CURL_HPP_
#define __CURL_HPP_

#include <curl.h>
#include <functional>

namespace curl {

// Warpper a C style class to c++ class.
// https://gitee.com/dzlua/test/blob/master/WrapperC/wrapper.hpp
template <typename T, typename C>
class Wrapper {
  public:
    Wrapper(C *cptr = nullptr) noexcept { reset(cptr); }
    Wrapper(const Wrapper<T,C>& r) noexcept { reset(r.get()); }
    Wrapper<T,C>& operator=(const Wrapper<T,C>& r) noexcept { reset(r.get()); return *this; }
    Wrapper<T,C>& operator=(C *cptr) noexcept { reset(cptr); return *this; }
  public:
    inline T* operator->() noexcept { return &obj_; }
    inline operator C*() const noexcept { return get(); }
    inline explicit operator bool() const noexcept { return get() != nullptr; }
  public:
    inline C* get() const noexcept { return obj_._get(); }
    inline C*& set() noexcept { return obj_._set(); }
    inline void reset(C *cptr = nullptr) noexcept { obj_._reset(cptr); }
    inline void swap(Wrapper<T,C>& r) noexcept { obj_._swap(r.obj_); }
  protected:
    T obj_;
};

template <typename T, typename C>
inline bool operator==(const Wrapper<T,C>& l, const Wrapper<T,C>& r) noexcept { return l.get() == r.get(); }
template <typename T, typename C>
inline bool operator==(const Wrapper<T,C>& l, std::nullptr_t) noexcept { return l.get() == nullptr; }
template <typename T, typename C>
inline bool operator==(std::nullptr_t, const Wrapper<T,C>& r) noexcept { return nullptr == r.get(); }
template <typename T, typename C>
inline bool operator!=(const Wrapper<T,C>& l, const Wrapper<T,C>& r) noexcept { return l.get() != r.get(); }
template <typename T, typename C>
inline bool operator!=(const Wrapper<T,C>& l, std::nullptr_t) noexcept { return l.get() != nullptr; }
template <typename T, typename C>
inline bool operator!=(std::nullptr_t, const Wrapper<T,C>& r) noexcept { return nullptr != r.get(); }

template <typename T, typename C>
class ClassWrapped {
    friend class Wrapper<T,C>;
  protected:
    ClassWrapped() : cptr_(nullptr) {}
    virtual ~ClassWrapped() { _reset(nullptr); }
    inline C* _get() const noexcept { return cptr_; }
    inline C*& _set() noexcept { return cptr_; }
    inline void _reset(C *cptr = nullptr) noexcept { cptr_ = cptr; }
    inline void _swap(ClassWrapped<T,C>& r) noexcept { std::swap(cptr_, r.cptr_); }
  protected:
    C* cptr_;
};

// macros for wrapper json.
#define WRAPPER_CLASS_BEGIN(classname,jsonstruct) \
  class classname : public ClassWrapped<classname, jsonstruct> { \
      friend class Wrapper<classname,jsonstruct>; \
    protected: \
      classname() = default; \
    public: \
      typedef Wrapper<classname,jsonstruct> Wrapped;
#define WRAPPER_CLASS_END };

namespace callback {
  typedef curl_progress_callback Progress;
  typedef curl_xferinfo_callback Xferinfo;
  typedef curl_write_callback Write;
  typedef curl_resolver_start_callback ResolverStart;
  typedef curl_chunk_bgn_callback ChunkBgn;
  typedef curl_chunk_end_callback ChunkEnd;
  typedef curl_fnmatch_callback Fnmatch;
  typedef curl_seek_callback Seek;
  typedef curl_read_callback Read;
  typedef curl_trailer_callback Trailer;
  typedef curl_sockopt_callback Sockopt;
  typedef curl_opensocket_callback OpenSocket;
  typedef curl_closesocket_callback CloseSocket;
  typedef curl_ioctl_callback Ioctl;
  typedef curl_malloc_callback Malloc;
  typedef curl_free_callback Free;
  typedef curl_realloc_callback Realloc;
  typedef curl_strdup_callback Strdup;
  typedef curl_calloc_callback Calloc;
  typedef curl_debug_callback Debug;
  typedef curl_conv_callback Conv;
  typedef curl_ssl_ctx_callback SslCtx;
  typedef curl_sshkeycallback Sshkey;
  typedef curl_formget_callback Formget;
  typedef curl_lock_function Lock;
  typedef curl_unlock_function Unlock;
  typedef curl_socket_callback Socket;
  typedef curl_multi_timer_callback MultiTimer;
} // end namespace callback

typedef curl_off_t off_t;

namespace detail {
  WRAPPER_CLASS_BEGIN(Slist, curl_slist)
    inline void append(const char *str) {
      cptr_ = curl_slist_append(cptr_, str); }
    inline void freeAll() { curl_slist_free_all(cptr_); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(MimePart, curl_mimepart)
    inline CURLcode setName(const char *name) { return curl_mime_name(cptr_, name); }
    inline CURLcode setFileName(const char *filename) { return curl_mime_filename(cptr_, filename); }
    inline CURLcode setType(const char *type) { return curl_mime_type(cptr_, type); }
    inline CURLcode setEncoder(const char *encoding) { return curl_mime_encoder(cptr_, encoding); }
    inline CURLcode setData(const char *data, size_t datasize) { return curl_mime_data(cptr_, data, datasize); }
    inline CURLcode setFiledata(const char *filename) { return curl_mime_filedata(cptr_, filename); }
    inline CURLcode setDataCallback(curl_off_t datasize
        , callback::Read readfunc
        , callback::Seek seekfunc
        , callback::Free freefunc
        , void *arg) { return curl_mime_data_cb(cptr_, datasize
      , readfunc, seekfunc, freefunc, arg); }
    inline CURLcode setSubparts(curl_mime *subparts) {
      return curl_mime_subparts(cptr_, subparts); }
    inline CURLcode setHeaders(curl_slist *headers, bool ownership) {
      return curl_mime_headers(cptr_, headers, ownership); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(Mime, curl_mime)
    inline void free() { curl_mime_free(cptr_); }
    inline MimePart::Wrapped addpart() {
      return MimePart::Wrapped(curl_mime_addpart(cptr_)); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(Easy, CURL)
    inline char* escape(const char *string, int length) {
      return curl_easy_escape(cptr_, string, length); }
    inline char* unescape(const char *string, int length, int *outlength) {
      return curl_easy_unescape(cptr_, string, length, outlength); }
    inline CURLcode pause(int bitmask) {
      return curl_easy_pause(cptr_, bitmask); }
      
    template <typename ...Args>
    inline CURLcode setopt(CURLoption option, Args ... args) {
      return curl_easy_setopt(cptr_, option, args...); }
    inline CURLcode perform() {
      return curl_easy_perform(cptr_); }
    inline void cleanup() { curl_easy_cleanup(cptr_); }
    template <typename ...Args>
    inline CURLcode getinfo(CURLINFO info, Args ... args) {
      return curl_easy_getinfo(cptr_, info, args...); }
    
    inline Wrapped duphandle() {
      return Wrapped(curl_easy_duphandle(cptr_)); }
    inline void reset() { curl_easy_reset(cptr_); }

    inline CURLcode recv(void *buffer, size_t buflen, size_t *n) {
      return curl_easy_recv(cptr_, buffer, buflen, n); }
    inline CURLcode send(const void *buffer, size_t buflen, size_t *n) {
      return curl_easy_send(cptr_, buffer, buflen, n); }

    inline CURLcode upkeep() { return curl_easy_upkeep(cptr_); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(Multi, CURLM)
    inline CURLMcode addHandle(CURL *curl_handle) {
      return curl_multi_add_handle(cptr_, curl_handle); }
    inline CURLMcode removeHandle(CURL *curl_handle) {
      return curl_multi_remove_handle(cptr_, curl_handle); }
    inline CURLMcode fdset( fd_set *read_fd_set
        , fd_set *write_fd_set
        , fd_set *exc_fd_set, int *max_fd) {
      return curl_multi_fdset(cptr_, read_fd_set, write_fd_set
        , exc_fd_set, max_fd); }
    inline CURLMcode wait(curl_waitfd extra_fds[]
        , unsigned int extra_nfds, int timeout_ms, int *ret) {
      return curl_multi_wait(cptr_, extra_fds , extra_nfds
        , timeout_ms, ret); }
    inline CURLMcode poll(curl_waitfd extra_fds[]
        , unsigned int extra_nfds, int timeout_ms, int *ret) {
      return curl_multi_poll(cptr_, extra_fds , extra_nfds
        , timeout_ms, ret); }
    inline CURLMcode wakeup() { return curl_multi_wakeup(cptr_); }
    inline CURLMcode perform(int *running_handles) {
      return curl_multi_perform(cptr_, running_handles); }
    inline CURLMcode cleanup() { return curl_multi_cleanup(cptr_); }

    inline CURLMsg* infoRead(int *msgs_in_queue) {
      return curl_multi_info_read(cptr_, msgs_in_queue); }

    inline CURLMcode socket(curl_socket_t s, int *running_handles) {
      return curl_multi_socket(cptr_, s, running_handles); }
    inline CURLMcode socketAction(curl_socket_t s, int ev_bitmask, int *running_handles) {
      return curl_multi_socket_action(cptr_, s, ev_bitmask, running_handles); }
    inline CURLMcode socketAll(int *running_handles) {
      return curl_multi_socket_all(cptr_, running_handles); }
      
    inline CURLMcode timeout(long *milliseconds) {
      return curl_multi_timeout(cptr_, milliseconds); }
      
    template <typename ...Args>
    inline CURLMcode setopt(CURLMoption option, Args ... args) {
      return curl_multi_setopt(cptr_, option, args...); }
      
    inline CURLMcode assign(curl_socket_t sockfd, void *sockp) {
      return curl_multi_assign(cptr_, sockfd, sockp); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(Share, CURLSH)
    template <typename ...Args>
    inline CURLSHcode setopt(CURLSHoption option, Args ... args) {
      return curl_share_setopt(cptr_, option, args...); }
    inline CURLSHcode cleanup() { return curl_share_cleanup(cptr_); }
  WRAPPER_CLASS_END

  WRAPPER_CLASS_BEGIN(Url, CURLU)
    inline void cleanup() { curl_url_cleanup(cptr_); }
    inline Wrapped dup() {
      return Wrapped(curl_url_dup(cptr_)); }
    inline CURLUcode get(CURLUPart what, char **part, unsigned int flags) {
      return curl_url_get(cptr_, what, part, flags); }
    inline CURLUcode set(CURLUPart what, const char *part, unsigned int flags) {
      return curl_url_set(cptr_, what, part, flags); }
  WRAPPER_CLASS_END
} // end namespace detail

typedef detail::Slist::Wrapped Slist;
typedef detail::Mime::Wrapped Mime;
typedef detail::MimePart::Wrapped MimePart;
typedef detail::Easy::Wrapped Easy;
typedef detail::Multi::Wrapped Multi;
typedef detail::Share::Wrapped Share;
typedef detail::Url::Wrapped Url;

inline Mime mime(CURL *easy) { return Mime(curl_mime_init(easy)); }
inline Slist slist() { return Slist(); }
inline Easy easy() { return Easy(curl_easy_init()); }
inline Multi multi() { return Multi(curl_multi_init()); }
inline Share share() { return Share(curl_share_init()); }
inline Url url() { return Url(curl_url()); }

// curl_formadd
// curl_formget
// curl_formfree

inline char* getenv(const char *variable) { return curl_getenv(variable); }
inline char* version() { return curl_version(); }
inline char* escape(const char *string, int length) {
  return curl_escape(string, length); }
inline char* unescape(const char *string, int length) {
  return curl_unescape(string, length); }

inline bool strequal(const char *s1, const char *s2) { return curl_strequal(s1, s2); }
inline bool strnequal(const char *s1, const char *s2, size_t n) { return curl_strnequal(s1, s2, n); }

inline void free(void *p) { curl_free(p); }

inline CURLcode globalInit(long flags) { return curl_global_init(flags); }
inline CURLcode globalInitMem(long flags
    , callback::Malloc m, callback::Free f
    , callback::Realloc r, callback::Strdup s
    , callback::Calloc c) {
  return curl_global_init_mem(flags, m, f, r, s, c); }
inline void globalCleanup() { curl_global_cleanup(); }
inline CURLsslset globalSslSet(curl_sslbackend id
    , const char *name, const curl_ssl_backend ***avail) {
  return curl_global_sslset(id, name, avail); }

inline time_t getdate(const char *p, const time_t *unused) { curl_getdate(p, unused); }
inline curl_version_info_data* versionInfo(CURLversion v) {
  return curl_version_info(v); }

inline const char* strError(CURLcode code) { return curl_easy_strerror(code); }
inline const char* strError(CURLSHcode code) { return curl_share_strerror(code); }
inline const char* strError(CURLMcode code) { return curl_multi_strerror(code); }

inline char* pushheaderByNum(curl_pushheaders *h, size_t num) {
  return curl_pushheader_bynum(h, num); }
inline char* pushheaderByName(curl_pushheaders *h, const char *name) {
  return curl_pushheader_byname(h, name); }

} // end namespace curl

#endif // __CURL_HPP_